library GetNearest initializer Init requires GroupUtils
//===========================================================================
// Information:
//==============
//
//     GetNearest is a very simple library that allows you to find the nearest
// unit, destructable, or item to a specified point within a specified radius.
// You can use a code filter as well, just like native Enum functions. All
// units/destructables/items outside of the playable map area will be ignored.
//
//===========================================================================
// GetNearest API:
//=================
//
// GetNearestUnit(x, y, radius, code) -> unit
//     This function returns the closest unit to the specified x/y point within
//  the specified radius matching the specified code filter. This function
//  will consider any unit whose collision radius overlaps the specified radius.
//
// GetNearestDestructable(x, y, radius, code) -> destructable
//     This function returns the closest destructable to the specified x/y point
//  within the specified radius matching the specified code filter.
//
// GetNearestItem(x, y, radius, code) -> item
//     This function returns the closest item to the specified x/y point within
//  the specified radius matching the specified code filter.
//
//===========================================================================

globals
    private real MAX_VALUE = 10000000000. //Larger than any squared distance value.
    private region MapArea
    private real MaxX
    private real MinX
    private real MaxY
    private real MinY
endglobals

//===========================================================================

private function GetNearestUnit_Sub takes unit nearest, real x, real y, real radius, code callback returns unit
    local group g = NewGroup()
    local real nearestdist = MAX_VALUE
    local real dist
    local unit u
    local boolexpr b = Condition(callback)
        call GroupEnumUnitsInRange(g, x, y, radius+197.00, b) //This function takes collision radius into account.
        loop //Iterate through the units in the group and find the closest to the specified point.
            set u = FirstOfGroup(g)
            exitwhen u == null
            set dist = Pow(x - GetUnitX(u), 2.) + Pow(y - GetUnitY(u), 2.)
            if IsUnitInRegion(MapArea, u) and dist < nearestdist then
                set nearestdist = dist
                set nearest = u
            endif
            call GroupRemoveUnit(g, u)
        endloop
    call ReleaseGroup(g)
    call DestroyBoolExpr(b)
    set g=null
    set u=null
    set b=null
    return nearest //Local handle parameters are automatically nulled.
endfunction

function GetNearestUnit takes real x, real y, real radius, code callback returns unit
    return GetNearestUnit_Sub(null, x, y, radius, callback)
endfunction //Avoids leaking a reference to the returned unit.

//===========================================================================

globals
    private rect enumrect = Rect(0., 0., 0., 0.)
    private real nearestdist = MAX_VALUE
    private real enumradius = 0.
    private real enumx = 0.
    private real enumy = 0.
endglobals

//! textmacro GetNearestFunc takes NAME, TYPE
globals
    private $TYPE$ nearest$TYPE$ = null
endglobals

private function GetNearest$NAME$_Enum takes nothing returns nothing
    local real dist = Pow(enumx - Get$NAME$X(GetEnum$NAME$()), 2.) + Pow(enumy - Get$NAME$Y(GetEnum$NAME$()), 2.)
        if dist <= enumradius and dist < nearestdist then
            set nearest$TYPE$ = GetEnum$NAME$()
            set nearestdist = dist
        endif
endfunction //Enumerate through the dests/items and find the closest one to the point.

private function GetNearest$NAME$_Sub takes $TYPE$ nearest, real x, real y, real radius, code callback returns $TYPE$
    local real old_enumradius = enumradius //Cache the previous values of the globals.
    local real old_enumx = enumx
    local real old_enumy = enumy
    local boolexpr b = Condition(callback)
        set enumradius = radius*radius //Assign new values to the globals for this call.
        set enumx = x
        set enumy = y

        call SetRect(enumrect, RMaxBJ(x - radius, MinX), RMaxBJ(y - radius, MinY), RMinBJ(x + radius, MaxX), RMinBJ(y + radius, MaxY))
        //Adjust the rect to cover the radius of the enueration. Make sure the rect stays within map boundries.
        call Enum$NAME$sInRect(enumrect, b, function GetNearest$NAME$_Enum) //Find the closest dest/item to the point.
        set nearest = nearest$TYPE$ //Assign the closest dest/item to a local variable.

        set nearest$TYPE$ = null    //Restore the previous values for the globals for nested enumerations.
        set nearestdist = MAX_VALUE
        set enumradius = old_enumradius
        set enumx = old_enumx
        set enumy = old_enumy
    call DestroyBoolExpr(b)
    set b=null
    return nearest //Local handle parameters are automatically nulled.
endfunction

function GetNearest$NAME$ takes real x, real y, real radius, code callback returns $TYPE$
    return GetNearest$NAME$_Sub(null, x, y, radius, callback)
endfunction //Avoids leaking a reference to the returned dest/item.
//! endtextmacro

//! runtextmacro GetNearestFunc("Destructable", "destructable")
//! runtextmacro GetNearestFunc("Item", "item")

//===========================================================================

private function Init takes nothing returns nothing
    set MapArea = CreateRegion()                      //Creating a region inside a global block fails, so do it here.
    set MaxX = GetRectMaxX(bj_mapInitialPlayableArea) //Store the boundries in globals to ensure that dests and items
    set MinX = GetRectMinX(bj_mapInitialPlayableArea) //are within map boundries.
    set MaxY = GetRectMaxY(bj_mapInitialPlayableArea)
    set MinY = GetRectMinY(bj_mapInitialPlayableArea)
    call RegionAddRect(MapArea, bj_mapInitialPlayableArea) //Add the map rect to a region for IsUnitInRegion().
endfunction

endlibrary

